﻿Imports System.Data.SqlClient
Imports System.Security.Cryptography
Imports System.Web.Configuration
Imports GuildCMS.Common

Namespace Account
    Public Class Register
        Inherits System.Web.UI.Page

#Region " Database variables "

        ''' <summary>
        ''' Set the database connection variables used throughout this class.
        ''' </summary>
        ''' <remarks>Placed here so they will not need to be typed out each time they are needed.</remarks>
        Private connectionString As String = WebConfigurationManager.ConnectionStrings("SqlConnectionString").ConnectionString()
        Private connection As SqlConnection = New SqlConnection(connectionString)
        Private command As New SqlCommand

#End Region

#Region " Page events "

        ''' <summary>
        ''' Handles the page load events.
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            ' Add "equalTo" value to both the txtPassword2 and txtEmail2 text fields to enable jQuery validate match checking.
            txtPassword2.Attributes.Add("equalTo", "#" & txtPassword1.ClientID)
            txtEmail2.Attributes.Add("equalTo", "#" & txtEmail1.ClientID)

            ' Set the minimum length for the password for jQuery Validate plugin.
            txtPassword1.Attributes.Add("MinLength", CInt(GetSetting("PasswordLength")))
            txtPassword2.Attributes.Add("MinLength", CInt(GetSetting("PasswordLength")))

            '' Add choice between reCAPTCHA and custom made CAPTCHA code once made.
            '' Until then we will use reCAPTCHA as the CAPTCHA solution.
            ' reCAPTCHA keys in the database are currently registered to http://localhost/...

            ' Get the reCaptcha key settings from the database.
            reCaptcha.PublicKey = CStr(GetSetting("reCaptchaPublic"))
            reCaptcha.PrivateKey = CStr(GetSetting("reCaptchaPublic"))

            ' Make sure the registration form is visible and the form complete instructions are not.
            panForm.Visible = True
            panInstructions.Visible = False
        End Sub

#End Region

#Region " Click event for when the registration form is submitted. "

        ''' <summary>
        ''' Handles the Click event of the registration button (btnRegister).
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
        Protected Sub btnRegister_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnRegister.Click
            ' Send activation email using the SendMail() procedure.
            If Page.IsValid Then
                ' Generate activation code.
                Dim activationCode As String = MakeRandomString(10)

                ' Get the MD5 salt from the database.
                Dim salt As String = CStr(GetSetting("PasswordSalt"))

                ' Insert information into the database
                Try
                    command = New SqlCommand("sp_public_addMember", connection)
                    command.CommandType = CommandType.StoredProcedure
                    command.Parameters.Add("@Login", SqlDbType.NVarChar, 25)
                    command.Parameters("@Login").Value = txtLogin.Text.ToLower
                    command.Parameters.Add("@Password", SqlDbType.NChar, 32)
                    command.Parameters("@Password").Value = HashPassword(txtPassword1.Text, salt)
                    command.Parameters.Add("@Code", SqlDbType.NChar, 10)
                    command.Parameters("@Code").Value = activationCode
                    command.Parameters.Add("@Email", SqlDbType.NVarChar, 100)
                    command.Parameters("@Email").Value = txtEmail1.Text
                    connection.Open()
                    command.ExecuteNonQuery()
                Catch ex As Exception

                Finally
                    connection.Close()
                End Try

                ' Generate the e-mail message body.
                Dim body As String = GenerateBody(txtEmail1.Text, activationCode)

                ' Set variables used to send the activation e-mail.
                Dim subject As String = CStr(GetSetting("EmailSubjectRegister"))

                ' Send the activation e-mail.
                SendMail(txtEmail1.Text, subject, body)

                ' Hide the registration form from the visitor and display instructions for activation.
                panForm.Visible = False
                panInstructions.Visible = True
            End If
        End Sub

#End Region

#Region " Form OnServerValidate procedures. "

        ''' <summary>
        ''' Handles the ServerValidate event of the valLogin control.
        ''' </summary>
        ''' <param name="source">The source of the event.</param>
        ''' <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs" /> instance containing the event data.</param>
        Protected Sub valLogin_ServerValidate(ByVal source As Object, ByVal args As ServerValidateEventArgs)
            ' Set args.IsValid to True by default.
            args.IsValid = True

            ' Check that the login has any content.
            If txtLogin.Text = Nothing Then
                valLogin.ErrorMessage = "This field is required."
                args.IsValid = False
            End If

            ' Check that the length of the login supplied is at least 3 characters long.
            If txtLogin.Text.Length() < 3 Then
                valLogin.ErrorMessage = "Please enter at least 3 characters."
                args.IsValid = False
            End If

            ' Check that the length of the login supplied is no more then 25 charaters in length.
            If txtLogin.Text.Length() > 25 Then
                valLogin.ErrorMessage = "Please enter no more then 25 charaters."
                args.IsValid = False
            End If

            ' Check that the supplied login is alphanumeric or contain the charaters ".", "_" and "-".
            Dim loginRegex As New Regex("[a-zA-Z0-9._-]")
            If loginRegex.IsMatch(txtLogin.Text) = False Then
                valLogin.ErrorMessage = "Login can only contain alphanumeric characters, ""."", ""_"" or ""-""."
                args.IsValid = False
            End If

            ' Check that the login does not already exist within the database.
            Try
                command = New SqlCommand("sp_public_countLogins", connection)
                command.CommandType = CommandType.StoredProcedure
                command.Parameters.Add("@Login", SqlDbType.NVarChar, 25)
                command.Parameters("@Login").Value = txtLogin.Text.ToLower
                command.Parameters.Add("@Total", SqlDbType.Int)
                command.Parameters("@Total").Direction = ParameterDirection.Output
                connection.Open()
                command.ExecuteNonQuery()
                Dim totalRows As Integer = CInt(command.Parameters("@Total").Value)
                If totalRows > 0 Then
                    valLogin.ErrorMessage = "Login name already exists."
                    args.IsValid = False
                End If
            Catch ex As Exception
                ' Any exception will cause args.IsValid to equal False.
                valLogin.ErrorMessage = "There was an error processing this field."
                args.IsValid = False
            Finally
                connection.Close()
            End Try
        End Sub

        ''' <summary>
        ''' Handles the ServerValidate event of the valPassword1 control.
        ''' </summary>
        ''' <param name="source">The source of the event.</param>
        ''' <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs" /> instance containing the event data.</param>
        Protected Sub valPassword1_ServerValidate(ByVal source As Object, ByVal args As ServerValidateEventArgs)
            ' Check the length of the password supplied in the txtPassword1 text box.
            args.IsValid = True

            ' Check that password1 has any content.
            If txtPassword1.Text = Nothing Then
                valPassword1.ErrorMessage = "This field is required."
                args.IsValid = False
            End If

            ' Check that password1 meets the minimum size requirment.
            If txtPassword1.Text.Length() < CInt(GetSetting("PasswordLength")) Then
                valPassword1.ErrorMessage = "Please enter at least " & CStr(GetSetting("PasswordLength")) & " characters."
                args.IsValid = False
            End If

            ' Check that password1 in no more then 32 charaters in length.
            If txtPassword1.Text.Length() > 32 Then
                valPassword1.ErrorMessage = "Please enter no more then 32 charaters."
                args.IsValid = False
            End If
        End Sub

        ''' <summary>
        ''' Handles the ServerValidate event of the valPassword2 control.
        ''' </summary>
        ''' <param name="source">The source of the event.</param>
        ''' <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs" /> instance containing the event data.</param>
        Protected Sub valPassword2_ServerValidate(ByVal source As Object, ByVal args As ServerValidateEventArgs)
            'Check that the password supplied in txtPassword1 is the same as the one supplied in txtPassword2.
            If txtPassword1.Text = txtPassword2.Text Then
                args.IsValid = True
            Else
                valPassword2.ErrorMessage = "Passwords did not match."
                args.IsValid = False
            End If
        End Sub

        ''' <summary>
        ''' Handles the ServerValidate event of the valEmail1 control.
        ''' </summary>
        ''' <param name="source">The source of the event.</param>
        ''' <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs" /> instance containing the event data.</param>
        Protected Sub valEmail1_ServerValidate(ByVal source As Object, ByVal args As ServerValidateEventArgs)
            ' Check that a vaild email was entered.
            args.IsValid = True

            ' Check that email1 has any content.
            If txtEmail1.Text = Nothing Then
                valEmail1.ErrorMessage = "This field is required."
                args.IsValid = False
            End If

            ' Check that the length of the email address supplied is no more then 100 charaters.
            If txtEmail1.Text.Length() > 100 Then
                valEmail1.ErrorMessage = valLogin.ErrorMessage = "Please enter no more then 100 charaters."
                args.IsValid = False
            End If

            ' Check that a valid e-mail was entered using a regular expression.
            Dim emailRegex As New Regex("^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$")
            If emailRegex.IsMatch(txtEmail1.Text) = False Then
                valEmail1.ErrorMessage = "Please enter a valid email address."
                args.IsValid = False
            End If

            ' Check that the supplied email address does not already exist within the database.
            Try
                command = New SqlCommand("sp_public_countEmails", connection)
                command.CommandType = CommandType.StoredProcedure
                command.Parameters.Add("@Email", SqlDbType.NVarChar, 100)
                command.Parameters("@Email").Value = txtEmail1.Text
                command.Parameters.Add("@Total", SqlDbType.Int)
                command.Parameters("@Total").Direction = ParameterDirection.Output
                connection.Open()
                command.ExecuteNonQuery()
                Dim totalRows As Integer = CInt(command.Parameters("@Total").Value)
                If totalRows > 0 Then
                    valEmail1.ErrorMessage = "Email address already exists."
                    args.IsValid = False
                End If
            Catch ex As Exception
                ' Any exception will cause args.IsValid to equal False.
                valEmail1.ErrorMessage = "There was an error processing this field."
                args.IsValid = False
            Finally
                connection.Close()
            End Try
        End Sub

        ''' <summary>
        ''' Handles the ServerValidate event of the valEmail2 control.
        ''' </summary>
        ''' <param name="source">The source of the event.</param>
        ''' <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs" /> instance containing the event data.</param>
        Protected Sub valEmail2_ServerValidate(ByVal source As Object, ByVal args As ServerValidateEventArgs)
            'Check that the email address supplied in txtEmail1 is the same as the one supplied in txtEmail2.
            If txtEmail1.Text = txtEmail2.Text Then
                args.IsValid = True
            Else
                valEmail2.ErrorMessage = "Emails did not match."
                args.IsValid = False
            End If
        End Sub

#End Region

#Region " Data generation specific functions. "

        ''' <summary>
        ''' Hashes the password supplied via the form.
        ''' </summary>
        ''' <param name="password">The password supplied via the form.</param>
        ''' <param name="salt">The salt used when generating the MD5 hash.</param>
        ''' <returns></returns>
        Private Function HashPassword(ByVal password As String, ByVal salt As String) As String
            ' Create an MD5 hash of the supplied password.
            Dim sourceText As String = salt + password
            Dim asciiEnc As New ASCIIEncoding
            Dim hash As String = ""
            Dim byteSourceText() As Byte = asciiEnc.GetBytes(sourceText)
            Dim md5Hash As New MD5CryptoServiceProvider
            Dim byteHash() As Byte = md5Hash.ComputeHash(byteSourceText)
            For Each b As Byte In byteHash
                hash &= b.ToString("x2")
            Next

            ' Return the hashed password
            Return hash
        End Function

        ''' <summary>
        ''' Generates a random activation code.
        ''' </summary>
        ''' <param name="size">The length we want the string to be.</param>
        ''' <returns>Returns the randomly generated activation code user to activate the new account.</returns>
        Private Function MakeRandomString(ByVal size As Integer) As String
            Dim rand As New Random()
            ' Characters we will use to generate this random string.
            Dim allowableChars() As Char = "ABCDEFGHIJKLOMNOPQRSTUVWXYZ0123456789".ToCharArray()

            ' Start generating the random string.
            Dim activationCode As String = String.Empty
            For i As Integer = 0 To size - 1
                activationCode += allowableChars(rand.Next(allowableChars.Length - 1))
            Next

            ' Return the random string in upper case.
            Return activationCode.ToUpper()
        End Function

#End Region

#Region " Activation e-mail specific functions and procedures. "

        ''' <summary>
        ''' Generates the body of the activation e-mail message.
        ''' </summary>
        ''' <param name="email">The email address supplied by the registrant.</param>
        ''' <param name="code">The activation code generated earlier.</param>
        ''' <returns>The body of the registration activation e-mail.</returns>
        Private Function GenerateBody(ByVal email As String, ByVal code As String) As String
            ' Retreive activation e-mail body template from the database.
            Dim body As String = CStr(GetSetting("EmailBodyRegister"))

            ' Retreive server site settings used in the construction of the activation url string.
            Dim serverProtocol As String = CStr(GetSetting("ServerProtocol"))
            Dim serverDomain As String = CStr(GetSetting("ServerDomain"))
            Dim serverPort As String = CStr(GetSetting("ServerPort"))
            Dim serverPath As String = CStr(GetSetting("ServerPath"))

            ' If the protocol is set to http and the port is 80 set port variable to nothing.
            If serverProtocol = "http://" And serverPort = "80" Then
                serverPort = Nothing
            End If

            ' If the protocol is set to https and the port is 443 set the port variable to nothing.
            If serverProtocol = "https://" And serverPort = "443" Then
                serverPort = Nothing
            End If

            ' Replace special strings with dynamically generated ones.
            body = body.Replace("[[CODE]]", code)
            body = body.Replace("[[ACTIVATIONURL]]", serverProtocol & serverDomain & serverPath & "Account/Activate.aspx?email=" & email & "&code=" & code)

            ' Return the message body.
            Return body
        End Function

#End Region

    End Class
End Namespace